import * as ts from 'typescript';

import {
	GetSw2ts,
	Special
} from '../config';
import { paramsInUrl } from '../constants';
import {
	handleStrRef,
	replaceUrlQurey,
	startEnumCase
} from '../utils/handleSchema';
import { findReqRefClass } from './inSearchFor';
import logger from './logger';

type Declaration = {
  default?: ts.TypeNode;
  name: string;
  constraint?: ts.TypeNode;
}[];
type CommentTrivia = ts.SyntaxKind.SingleLineCommentTrivia
| ts.SyntaxKind.MultiLineCommentTrivia;

const { factory } = ts;
const sw2ts = GetSw2ts();

/**
 * 用于存放数据类中的枚举类型相关信息；
 * 便于通过 leetEnumsCreator 函数，创建枚举数据；
 */
let leetEnums: AliasEnum[];
/**
 * 该函数用于在外部模块中访问 leetEnums 数组数据；
 * 数组 leetEnums 在 createMembers 函数中进行赋值处理；
 * @param callback Function
 * @returns
 */
export function leetEnumsCreator (callback: Function) {
	if (leetEnums.length) callback(leetEnums);
}

export function createKeywordTypeNode (keyword: ts.KeywordTypeSyntaxKind) {
	return factory.createKeywordTypeNode(keyword);
}

export function createTypeReferenceNode (reference: string) {
	return factory.createTypeReferenceNode(handleStrRef(reference));
}

/**
 * 用于生成 [数组类型] 的类型节点对象
 * @param reference string
 * @returns ts.ArrayTypeNode
 */
export function createArrayTypeNode (reference?: string) {
	const elementType = reference
		? createTypeReferenceNode(reference)
		: createKeywordTypeNode(ts.SyntaxKind.StringKeyword);

	return factory.createArrayTypeNode(elementType);
}

export function createQuestionToken (required?: boolean) {
	return required ? undefined : factory.createToken(ts.SyntaxKind.QuestionToken);
}

export function createPropertySignature (
	property: string | ts.PropertyName,
	typeNode: ts.TypeNode,
	required: boolean | undefined
) {
	return factory.createPropertySignature([], property, createQuestionToken(required), typeNode);
}

export function addSyntheticLeadingComment<T extends ts.Node> (
	node: T,
	kind: CommentTrivia,
	comment: string
) {
	return ts.addSyntheticLeadingComment(node, kind, comment, true);
}

const isCustomRef = (ref = 'any') => {
	return ref === 'object' ? 'any' : handleStrRef(ref);
};

/**
 * 该函数生成请求参数-属性类型节点信息
 * @param name string
 * @param type string
 * @param reference string
 * @returns ts.TypeNode
 */
export function createPropertyTypeNode (name: string, type?: string, reference?: string): ts.TypeNode {
	// 注意对类型不一致的字符串，进行替换处理
	reference = Special.includes(reference as string) ? 'number' : reference;

	if (type === 'ref' || reference === 'ref') {
		logger.warn(`Logger: 接口文档中存在类型不匹配的问题，请检查 ${name || '-'} 属性类型`);
	}

	if (reference && type !== 'array') return createTypeReferenceNode(reference);

	switch (type) {
	case 'number':
		return createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
	case 'integer':
		return createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
	case 'string':
		return createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
	case 'boolean':
		return createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
	case 'array':
		return createArrayTypeNode(isCustomRef(reference));
	case 'null':
		return factory.createLiteralTypeNode(factory.createNull());
	default:
		return createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
	}
}

export function createMembers (__ob__: Record<string, DefinitionSpec>, property: string) {
	// 置空数组数据，以便加入当前属性对象的最新数据，用于创建当前类对象的枚举对象
	leetEnums = [];
	const members: ts.TypeElement[] = [];
	const {
		properties, __r__, required = []
	} = (__ob__ || {})[property];

	const _require_ = sw2ts.required || __r__;

	for (const key in properties) {
		const {
			name, type, format, description, items, enum: aliasEnums
		} = properties[key];
		// 特殊场景：items: { type: "integer", format: "int32" }
		let reference = findReqRefClass(key, properties);

		// 异常情况处理，避免出现 items?.items ，即出现两次嵌套；
		// items: { items: { originalRef: 'CatClass' } }
		if (items?.items?.originalRef) {
			reference = items?.items?.originalRef;
		}

		// 用于创建当前类对象的枚举对象
		if (aliasEnums) {
			reference = startEnumCase(property, key);
			leetEnums.push({
				property: property,
				name: reference,
				items: aliasEnums,
				description: description,
			});
		}
		let _required = _require_ || required.includes(key);
		// 通常在新增数据时，没有ID字段，故修改为选填
		if (key.toLowerCase().includes('id')) _required = false;

		// 通过 sw2ts.required 条件判断来确认是否强制必填
		let core = createPropertySignature(key, createPropertyTypeNode(name, type, reference), _required);

		if (description) {
			core = addSyntheticLeadingComment(core, ts.SyntaxKind.MultiLineCommentTrivia, ` ${description} `);
		}
		if (format) {
			core = ts.addSyntheticTrailingComment(core, ts.SyntaxKind.SingleLineCommentTrivia, ` ${format}`, false);
		}

		members.push(core);
	}

	return members;
}

/**
 * 用于过滤数组中类型不一致的数据
 * @param literal string
 * @returns
 */
export function literalFilters (literal?: string) {
	return (__ob__: any) => {
		if (literal) {
			return typeof __ob__ !== literal;
		} else {
			literal = typeof __ob__;
		}
	};
}

/**
 * exceptional: object in query or path
 * @param parameter ParameterFace
 * @returns
 */
export function skipIncludes (parameter: ParameterFace) {
	return ['object'].includes(parameter.type) && paramsInUrl.includes(parameter.in);
}

/**
 * 该函数尚未使用，属于示例代码
 * @param callback ts.PropertyAccessExpression
 * @returns
 */
export function createBlock (callback: ts.PropertyAccessExpression) {
	return factory.createBlock([
		factory.createReturnStatement(
			factory.createCallExpression(callback, undefined, [
				factory.createIdentifier('required'),
				factory.createArrayLiteralExpression(),
				factory.createStringLiteral('exports'),
				factory.createObjectLiteralExpression([], /* multiline */ true),
				factory.createNull(),
				factory.createNumericLiteral(30),
				factory.createIdentifier('undefined'),
			])
		),
	]);
}

/**
 * 该函数尚未使用，属于示例代码
 * @returns
 */
export function createTemplateExpression () {
	return factory.createTemplateExpression(factory.createTemplateHead('/gen/'), [
		factory.createTemplateSpan(factory.createNumericLiteral('language'), factory.createTemplateMiddle('/')),
		factory.createTemplateSpan(factory.createNumericLiteral('leet'), factory.createTemplateTail('')),
	]);
}

export function createArgumentsArray (url: string, parameters: string[]) {
	const list: ts.Expression[] = [];
	if (url.includes('{') || url.includes('}')) {
		list.push(factory.createNumericLiteral('`' + replaceUrlQurey(url) + '`'));
	} else {
		list.push(factory.createStringLiteral(url, true));
	}
	for (const parameter of parameters) {
		if (url.includes(parameter)) continue;
		list.push(factory.createIdentifier(parameter));
	}

	return list;
}

export function createObjectLiteralExpression (parameters: string | string[], url?: string) {
	const shorthand: ts.ShorthandPropertyAssignment[] = [];
	for (const parameter of [].concat.call([], parameters)) {
		if (url?.includes(parameter)) continue;
		shorthand.push(factory.createShorthandPropertyAssignment(parameter));
	}

	return factory.createObjectLiteralExpression(shorthand);
}

/**
 * code example:
 * API.get(url: sting, { name: string; like?: boolean })
 * @param url string
 * @param parameters string[]
 * @returns
 */
export function createComposeArguments (url: string, parameters: string[]) {
	const list: ts.Expression[] = [];
	if (url.includes('{') || url.includes('}')) {
		list.push(factory.createNumericLiteral('`' + replaceUrlQurey(url) + '`'));
	} else {
		list.push(factory.createStringLiteral(url, true));
	}

	return list.concat(createObjectLiteralExpression(parameters, url));
}

export function includesBrackets (url = '') {
	return url.includes('{') || url.includes('}');
}

/**
 * 组装Url与参数，构成请求函数主体
 * @param url sting
 * @param parameters sting[]
 * @returns ts.Expression[]
 */
export function createCombineArguments (url: string, parameters: string[], ref?: string) {
	const listParameter: ts.Expression[] = [];
	if (parameters.length == 1) {
		parameters.unshift('undefined');
	}

	if (url.includes('?') || url.includes('$')) {
		// 该判断逻辑是针对 ['post', 'put', 'patch'] 请求方法
		listParameter.push(factory.createNumericLiteral('`' + url + '`'));
	} else {
		listParameter.push(factory.createStringLiteral(url, true));
	}

	// createObjectLiteralExpression(query_name)
	for (const name of parameters) {
		if (ref && name === 'state') {
			listParameter.push(factory.createNumericLiteral(ref));
		} else {
			listParameter.push(factory.createNumericLiteral(name));
		}
	}

	return listParameter;
}

export function createTypeLiteralNode (mapParameter: Record<string, ParamSpec>) {
	const listParameter: ts.PropertySignature[] = [];
	for (const key in mapParameter) {
		const {
			name, type, ref, required
		} = mapParameter[key];
		const literal = ['payload'].includes(key) ? key : name;
		const node = createPropertyTypeNode(literal, type, ref);
		listParameter.push(createPropertySignature(literal, node, required));
	}
	return factory.createTypeLiteralNode(listParameter);
}

export function handleUrlParameters (url: string, parameters: ParameterFace[]) {
	const list = [];
	let query = 'state';
	for (const param of parameters) {
		if (url.includes(param.name) || param.in === 'path') {
			continue;
		}
		if (param.in === 'query') {
			list.push(param.name + '=' + '${state.' + param.name + '}');
		} else {
			query = `state.${param.name}`;
		}
	}
	if (list.length) url += '?' + list.join('&');

	return {
		url,
		query,
	};
}

/**
 * @description
 * Usage: factory.createTypeReferenceNode('User')
 * @param declaration Declaration
 * @returns ts.TypeParameterDeclaration[]
 */
export function createTypeParameterDeclaration (declaration: Declaration) {
	return declaration.map((v) => factory.createTypeParameterDeclaration(undefined, v.name, v.constraint, v.default));
}
